#pragma once

#include<iostream>
#include<string>
#include<cstring>
#include <functional>
#include<sys/epoll.h>
#include"err.h"
#include"log.hpp"
#include"Sock.hpp"

namespace epoll_ns
{
    static const uint16_t defaultport = 8080;
    static const int size = 128;
    static const int defaultvalue = -1;
    static const int defaultnum = 64;
    using func_t = std::function<std::string(const std::string&)>;

    class EpollServer
    {
    public:
        EpollServer(func_t func,uint16_t port = defaultport,int num = defaultnum)
            : port_(port),listensock_(defaultvalue),
            epfd_(defaultvalue),num_(num),revs_(nullptr),func_(func)
        {
        }

        void initServer()
        {
            listensock_ = Sock::Socket();   // 创建套接字
            Sock::Bind(listensock_, port_); // 绑定套接字
            Sock::Listen(listensock_);      // 把套接字设置为监听状态！

            epfd_ = epoll_create(size);//创建epoll模型
            if(epfd_ == -1)//创建epoll模型失败！
            {
                LogMessage(FATAL,"epoll create error");
                exit(EPOLL_CREAT_ERR);
            }

            //将listensock添加进入epoll模型
            struct epoll_event evt;
            evt.events = EPOLLIN | EPOLLET;
            evt.data.fd = listensock_;//现在暂时将联合体当做fd看待！
            epoll_ctl(epfd_,EPOLL_CTL_ADD,listensock_,&evt);


            //申请就绪事件的空间！
            revs_ = new struct epoll_event[num_];

            LogMessage(NORMAL, "init server sucess!");
        }

        void HanderEvent(int readynum)
        {
            LogMessage(DEBUG,"HanderEvent begin!");
            for(int i =0;i<readynum;i++)
            {
                uint32_t events = revs_[i].events;//知道了是那样一个事件就绪了！
                //我们如何知道这个事件是哪个文件描述符上的呢？
                //我们在初始化的时候！evt.data.fd = listensock_;写过这个代码就是将data当做fd来看待！
                //这个fd的作用就是让事件就绪的时候，被重新捞上来的时候，我们就知道是哪一个fd就绪了！
                int sock = revs_[i].data.fd;

                if(sock == listensock_ && (events & EPOLLIN))
                {
                    //这是一个listensock的读事件就绪！——那么我们就要获取新连接！
                    std::string ClientIP;
                    uint16_t ClientPort;

                    int fd = Sock::Accept(sock,&ClientIP,&ClientPort);
                    if(fd<0)
                    {
                        LogMessage(WARNING, "Accept error!");
                        continue;
                    }
                    //获取套接字成功！那么我们可以直接读取吗？——肯定不可以！
                    //因为IO分两步——一个是等待，一个是拷贝！我们是多路转接！我们要等待+拷贝分离！
                    //所以我们要让其在epoll里面等待！
                    //我们使用epoll我们就不用像select和poll一样，遍历数组，然后找一个合适的位置插入！
                    //以前是因为select，和poll需要辅助数组告诉select和poll我们要让他们关心那个文件描述符
                    //现在我们只要插入epoll的红黑树里面就可以完成这个让epoll监视的任务！
                    //因为epoll模型里面的红黑树就已经取代了这个辅助数组的作用！
                    struct epoll_event evt;
                    evt.events = EPOLLIN;//这是一个读事件！
                    //我们是要从这个连接中读取客户端发过来的数据！
                    evt.data.fd = fd;
                    epoll_ctl(epfd_,EPOLL_CTL_ADD,fd,&evt);

                }
                else if(events & EPOLLIN)//说明是一个普通的读事件！
                {
                    char buff[1024];
                    int n = recv(sock,buff,sizeof(buff)-1,0);
                    //这里的读取是有问题的！我们如何保证是完整把数据读取完毕了呢？
                    //而且我们是不能循环读取的！因为循环读取只有第一次不用等待！那么第二次，第三次的时候就有可能要等待了！
                    //但是如果我们要通过再次等待就绪，然后再来读取，buff是一个临时变量！出了作用域就会被释放！
                    //那么数据就没有了！这要怎么解决？

                    if(n > 0)
                    {
                        buff[n] = 0;
                        LogMessage(DEBUG,"Client #%s",buff);

                        std::string response = func_(buff);

                        send(sock,response.c_str(),response.size(),0);

                    }
                    else if(n == 0)//对方把客户端关闭！
                    {
                        epoll_ctl(epfd_,EPOLL_CTL_DEL,sock,nullptr);//从epoll模型中移除这个文件描述符！
                        //我们知道epoll的底层有一个红黑树！
                        //既然是红黑树！那么就有kv值！而epoll的红黑树的key值就是fd！value是events！
                        //我们删除的时候我们不需要传入event参数！我们只要传入key值就可以！
                        close(sock);//关闭套接字！
                        //建议先移除，后关闭文件描述符,因为epoll移除的时候得保证sock是一个合法的sock，才能移除！否则会报错！

                        LogMessage(NORMAL,"client quit!");
                    }
                    else //读取出错！
                    {
                        epoll_ctl(epfd_,EPOLL_CTL_DEL,sock,nullptr);
                        close(sock);

                        LogMessage(ERROR,"recv error! code: %d,stringerr: %s",errno,strerror(errno));
                    }
                
                }
                else if(events & EPOLLOUT)//一个普通的写事件！
                {
                    ////.....
                }

            }
            LogMessage(DEBUG,"HandlerEvent end!");
        }

        void start()
        {
            int timeout = -1;
            for(;;)
            {
                int n = epoll_wait(epfd_,revs_,num_,timeout);
                //这样子事件就绪的结果就会被放进revs_里面了！
                switch (n)
                {
                case 0: // 超时等待
                    LogMessage(NORMAL, "time out!");
                    break;
                case -1: // 等待错误！
                    LogMessage(WARNING, "epoll_wait failed! errno %d,strerr %s", errno, strerror(errno));
                    break;
                default://事件已经就绪！
                    LogMessage(NORMAL, "have event ready!");
                    // HanderEvent(n);//需要知道已经就绪了！所以n要交给函数
                    break;
                }
            }
        }

        ~EpollServer()
        {
            if(listensock_ != defaultvalue) close(listensock_);
            if(epfd_ != defaultvalue)close(epfd_);
            if(revs_) delete[] revs_;

        }
    private:
        uint16_t port_;
        int listensock_;
        int epfd_;
        struct epoll_event* revs_;
        int num_;
        func_t func_;

    };
}
